#include "stdafx.h"
#include "Utils.h"
#include "FileNameUtils.h"
#include "ProgramOptions.h"
#include <string>
#include <iostream>

char* cEmptyDescription = "";
//Positional options
char* cInputSwitch  = "input";
char* cOutputSwitch = "output";

char* cHeader = "Helps to print large images using small size paper.\n\n"
				"Syntax\n\n    stencil2word [options] input-file  output-file\n\n"
				"Options\n\r\n\r";

// Help
char* cHelpSwitch   = "help";
char* cHelpSwitchDescription = "Display this message."; 

//Export type options
char* cWordSwitch   = "word";
char* cWordSwitchDescription = "Export an image to MS Word.\n";
char* cNote1 = "\tNote: You must have installed MS Word on your computer to use this option.\n\r";

char* cFolderSwitch = "folder";
//char* cFolderSwitchDescription = "Save image(s) to disk. Output-file param must be the name of folder (in case you split image), or the name of file, if you just resize image. This option is set by default."; 
char* cFolderSwitchDescription = "Save image(s) to disk. This option is set by default."; 

// Resizing options
char* cResizingOptionsHeader = "\tSpecifying the following options leads to resizing an input image.\n\r";

char* cNewHeight    = "new-height";
char* cNewHeightDescription = "Height of the output image.";

char* cNewWidth     = "new-width";
char* cNewWidthDescription = "Width of the output image."; 

// Splitting options
char* cSplittingOptionsHeader = "\tSpecifying the following options leads to splitting an output image.\n\r";

char* cPieceHeight  = "piece-height";
char* cPieceHeightDescription = "Width of the piece."; 

char* cPieceWidth   = "piece-width";
char* cPieceWidthDescription = "Height of the piece.\n\r";
char* cNote2 = "\tNotice:  This options can be used only with \"--folder\" switch, otherwise it will be ignored.\n\r";

char* cParseErrorMsg = "Error in options: ";
char* cUseToHelpSuggestion = "Type --help to get more information.";

// Output format options

char* cCompressQuality = "quality";
char* cCompressQualityDescription = "Compression quality of the output JPEG file(s). Must be in the range of 1 and 100. By default it is 70.";

char* cOutputFormat = "outformat";
#define cOutputFormatPossibleValues "[gif|jpeg|png|bmp]"
char* cOutputFormatDescription =  cOutputFormatPossibleValues " Format of the output file(s).";

const int cDefaultQualityWord = 100;
const int cDefaultQualityFile = 70;

CProgramOptions::CProgramOptions()
	:m_OptionsWasStored(false)
{
	m_MainOptions.add_options()
		(cInputSwitch, wvalue<std::wstring>(), cEmptyDescription)
		(cOutputSwitch, wvalue<std::wstring>(), cEmptyDescription);
	m_HelpOptions.add_options()
		(cHelpSwitch, cHelpSwitchDescription);
	
	m_WordOption.add_options()
		(cWordSwitch, cWordSwitchDescription);
	m_FolderOption.add_options()
		(cFolderSwitch, cFolderSwitchDescription);
		
	m_ResizingOptions.add_options()
		(cNewHeight, value<int>(), cNewHeightDescription)
		(cNewWidth, value<int>(), cNewWidthDescription);
	m_SplittingOptions.add_options()
		(cPieceHeight, value<int>(), cPieceHeightDescription)
		(cPieceWidth, value<int>(), cPieceWidthDescription);
	
	m_OutputFormatOptions.add_options()
		(cCompressQuality, value<int>(), cCompressQualityDescription)
		(cOutputFormat, wvalue<std::wstring>(), cOutputFormatDescription);
}

void CProgramOptions::PrintHelp()
{
	std::cout << cHeader;
	std::cout << m_HelpOptions << std::endl;
	std::cout << m_WordOption << cNote1 << std::endl;
	std::cout << m_FolderOption;
	std::cout << m_OutputFormatOptions;
	
	std::cout << std::endl << cResizingOptionsHeader << std::endl;
	std::cout << m_ResizingOptions;
	
	std::cout << std::endl << cSplittingOptionsHeader << std::endl;
	std::cout << m_SplittingOptions;
	std::cout << cNote2;
}

void CProgramOptions::SuggestToUseHelp()
{
	std::cout << cUseToHelpSuggestion << std::endl;
}

bool CProgramOptions::ParseCommandLine(int argc, TCHAR* argv[])
{
	positional_options_description PosOptions;
	PosOptions.add(cInputSwitch, 1).add(cOutputSwitch, 1);
	
	options_description AllOptions;
	AllOptions.add(m_MainOptions).add(m_HelpOptions).add(m_WordOption).add(m_FolderOption).add(m_ResizingOptions).add(m_SplittingOptions).add(m_OutputFormatOptions);

	try
	{
		store(basic_command_line_parser<TCHAR>(argc, argv).options(AllOptions).positional(PosOptions).run(), m_VariablesMap);
		notify(m_VariablesMap);
	}
	catch(error& e)
	{
		std::cout << cParseErrorMsg << e.what() << std::endl;
		SuggestToUseHelp();
		return false;
	}
	if( m_VariablesMap.count(cHelpSwitch) )
	{
		PrintHelp();
		return false;
	}
	if( !CheckOutputFormatOption() )
	{
		ShowWrongOutputFormatOptionMsg();
	}
	if( !CheckForNecessaryOptions() )
	{
		SuggestToUseHelp();
		return false;
	}
	if( !CheckForIncompatibleOptions() )
	{
		SuggestToUseHelp();
		return false;
	}
	m_OptionsWasStored = true;
	return true; 
}

void CProgramOptions::ShowWrongOutputFormatOptionMsg()
{
	std::cout << "Option \"" << cOutputFormat << "\" msut be one of the " << cOutputFormatPossibleValues " values." << std::endl;
}

bool CProgramOptions::CheckOutputFormatOption()
{
	if( m_VariablesMap.count(cOutputFormat) )
	{
		return GetExplicitOutputImageType() != imtUnknown;
	}
	return true;
}

void CProgramOptions::ShowIncompatibleSwitchesMsg(const char* FirstSwitch, const char* SecondSwitch)
{
	std::cout << "Option \"" << FirstSwitch << "\" is incompatible with \"" << SecondSwitch << "\"." << std::endl;
}

void CProgramOptions::ShowOptionMustBeGreaterThanZeroMsg(const char* OptionName)
{
	std::cout << "Option \"" << OptionName << "\" must be greater than zero." << std::endl;
}

void CProgramOptions::ShowNeedNecessaryOptionMsg(const char* OptionName)
{
	std::cout << "\"" << OptionName << "\" param required." << std::endl;
}

void CProgramOptions::ShowInvalidCompressQuailtyMsg()
{
	std::cout << "Option \"" << cCompressQuality << "\" must be in range 1..100" << std::endl;
}

bool CProgramOptions::CheckForNecessaryOptions()
{
	if ( !m_VariablesMap.count(cInputSwitch) )
	{
		ShowNeedNecessaryOptionMsg(cInputSwitch);
		return false;
	}
	return true;
}


bool CProgramOptions::CheckForIncompatibleOptions()
{
	if( m_VariablesMap.count(cFolderSwitch) &&
		m_VariablesMap.count(cWordSwitch) )
	{
		ShowIncompatibleSwitchesMsg(cFolderSwitch, cWordSwitch);
		return false;
	}
	if( m_VariablesMap.count(cNewHeight) )
	{
		if( m_VariablesMap[cNewHeight].as<int>() <= 0 )
		{
			ShowOptionMustBeGreaterThanZeroMsg(cNewHeight);
			return false;
		}
	}
	if( m_VariablesMap.count(cNewWidth) )
	{
		if( m_VariablesMap[cNewWidth].as<int>() <= 0 )
		{
			ShowOptionMustBeGreaterThanZeroMsg(cNewWidth);
			return false;
		}
	}
	if( m_VariablesMap.count(cPieceWidth) )
	{
		if( m_VariablesMap[cPieceWidth].as<int>() <= 0 )
		{
			ShowOptionMustBeGreaterThanZeroMsg(cPieceWidth);
			return false;
		}
	}
	if( m_VariablesMap.count(cPieceHeight) )
	{
		if( m_VariablesMap[cPieceHeight].as<int>() <= 0 )
		{
			ShowOptionMustBeGreaterThanZeroMsg(cPieceHeight);
			return false;
		}
	}
	if( m_VariablesMap.count(cCompressQuality) )
	{
		int CompressQuality = m_VariablesMap[cCompressQuality].as<int>();
		if( !((CompressQuality >= 1) && (CompressQuality <= 100)) )
		{
			ShowInvalidCompressQuailtyMsg();
			return false;
		}
	}
	return true;
}



int CProgramOptions::GetCompressQuality() const
{
	if( m_VariablesMap.count(cCompressQuality) )
		return  m_VariablesMap[cCompressQuality].as<int>();
	return (GetExportType() == exp2File)? cDefaultQualityFile:cDefaultQualityWord;
}

ExportType_t CProgramOptions::GetExportType() const
{
	_ASSERT( m_OptionsWasStored );
	ExportType_t ExplicitType = GetExplicitExportType();
	if( ExplicitType != expUnknown)
		return ExplicitType;
	tstring OutputFileName;
	GetExplicitOutputFileName(OutputFileName);
	if( OutputFileName.empty() || IsDirectoryName(OutputFileName) )
		return exp2File; // default
	tstring OutputFileExt;
	ExtractFileExt(OutputFileName, OutputFileExt);
	if( HasDocExt(OutputFileExt) )
		return exp2Word;
	return exp2File; 
}	

void CProgramOptions::GetExplicitOutputFileName(tstring& OutputFileName) const
{
	if( m_VariablesMap.count(cOutputSwitch) )
		OutputFileName = m_VariablesMap[cOutputSwitch].as<std::wstring>();
	else
		OutputFileName = _T("");
}

ImageType_t CProgramOptions::GetExplicitOutputImageType() const
{
	if( m_VariablesMap.count(cOutputFormat) )
	{
		tstring sOutputFormat = m_VariablesMap[cOutputFormat].as<std::wstring>();
		if( (_tcsicmp(sOutputFormat.c_str(), _T("jpeg")) == 0) ||
			(_tcsicmp(sOutputFormat.c_str(), _T("jpg")) == 0) )
			return imtJPEG;
		if( _tcsicmp(sOutputFormat.c_str(), _T("png")) == 0)
			return imtPNG;
		if( _tcsicmp(sOutputFormat.c_str(), _T("gif")) == 0)
			return imtGIF;
		if( _tcsicmp(sOutputFormat.c_str(), _T("bmp")) ==0)
			return imtBMP;
	}
	return imtUnknown;
}

ExportType_t CProgramOptions::GetExplicitExportType() const
{
	if( m_VariablesMap.count(cWordSwitch) )
		return exp2Word;
	if( m_VariablesMap.count(cFolderSwitch) )
		return exp2File;
	return expUnknown;
}

bool CProgramOptions::NeedResize() const
{
	_ASSERT( m_OptionsWasStored );
	return  m_VariablesMap.count(cNewHeight) ||
			m_VariablesMap.count(cNewWidth);
}

bool CProgramOptions::NeedSplit() const
{
	_ASSERT( m_OptionsWasStored );
	return m_VariablesMap.count(cPieceHeight) ||
		   m_VariablesMap.count(cPieceWidth);
}

void CProgramOptions::GetPieceSize(SIZE& PieceSize) const
{	
	if( m_VariablesMap.count(cPieceHeight) )
		PieceSize.cy = m_VariablesMap[cPieceHeight].as<int>();
	if( m_VariablesMap.count(cPieceWidth) )
		PieceSize.cx = m_VariablesMap[cPieceWidth].as<int>();
}

void CProgramOptions::GetNewSize(SIZE& NewSize) const
{
	if( m_VariablesMap.count(cNewWidth) )
		NewSize.cx = m_VariablesMap[cNewWidth].as<int>();
	if( m_VariablesMap.count(cNewHeight) )
		NewSize.cy = m_VariablesMap[cNewHeight].as<int>();
}

const ImageType_t cDefaultImgTypeForDoc = imtPNG;

ImageType_t CProgramOptions::GetOutputImageType(const tstring& InPath) const
{
	ImageType_t ExplicitImageType = GetExplicitOutputImageType();
	if( ExplicitImageType != imtUnknown)
		return ExplicitImageType;

	ExportType_t ExportType = GetExportType();
	if( ExportType == exp2Word )
		return cDefaultImgTypeForDoc; 

	tstring ExplicitOutputFileName;
	GetExplicitOutputFileName(ExplicitOutputFileName);
	
	if( !ExplicitOutputFileName.empty() )
	{
		if( HasDocExt(ExplicitOutputFileName) )
			return cDefaultImgTypeForDoc;
		ImageType_t OutputImageType = FileName2ImageType(ExplicitOutputFileName);
		if( OutputImageType != imtUnknown )
			return OutputImageType;
	}
	if( !InPath.empty() )
	{
		ImageType_t InputImageType = FileName2ImageType(InPath);
		if( InputImageType != imtUnknown )
			return InputImageType;
	}
	return imtUnknown;
}

void CProgramOptions::ShowNoMatchedPathsError() const
{
	std::cout << "Unable to find files matched to the input pattern." << std::endl;
}

void CProgramOptions::GetInputFileNames(std::vector<tstring>& InputFileNames) const
{
	if( m_VariablesMap.count(cInputSwitch) )
	{
		if( !PathContainsWildcards(m_VariablesMap[cInputSwitch].as<std::wstring>()) )
		{
			InputFileNames.push_back(m_VariablesMap[cInputSwitch].as<std::wstring>());
		}
		else
		{
			InputFileNames.clear();
			GetMatchedPaths(m_VariablesMap[cInputSwitch].as<std::wstring>(), InputFileNames);
			if( InputFileNames.size() == 0 )
				ShowNoMatchedPathsError();
		}
	}
	else
		_ASSERT( false );
}

void CProgramOptions::GetOutputFileNamePattern(const tstring& InPath, tstring& Prefix, tstring& Suffix) const
{
	_ASSERT( !InPath.empty() );
	
	tstring ExplicitOutputName;
	GetExplicitOutputFileName(ExplicitOutputName);

	if( ExplicitOutputName.empty() ) // Create the pattern based on input file name.
	{	
		// Prefix
		ExtractFileName_WithPath_WithoutExt(InPath, Prefix);
		// Suffix
		ExtractFileExt(InPath, Suffix);
	}
	else // Create the pattern based on --output param.
	{
		tstring ExplicitOutputName_Full;
		GetFullPath(ExplicitOutputName, ExplicitOutputName_Full); 
		if( IsDirectoryName(ExplicitOutputName) || IsDirectory(ExplicitOutputName_Full) )
		{
			// Prefix
			tstring InputFileName;
			ExtractFileName_WithoutPath_WithoutExt(InPath, InputFileName);
			IncludeTrailingPathDelimiter(ExplicitOutputName_Full, ExplicitOutputName_Full);
			Prefix = ExplicitOutputName_Full + InputFileName;
			// Suffix
			ExtractFileExt(InPath, Suffix);//
		}
		else
		{
			//Prefix
			ExtractFileName_WithPath_WithoutExt(ExplicitOutputName_Full, Prefix );
			// Suffix
			ExtractFileExt(ExplicitOutputName, Suffix);
		}
	}
	CorrectSuffixIfNecessary(Suffix);
}

void CProgramOptions::CorrectSuffixIfNecessary(tstring& Suffix) const
{
	ExportType_t ExportType = GetExportType();
	ImageType_t ExplicitOutputImageType = GetExplicitOutputImageType();
	if( ExportType == exp2File )
	{
		ImageType_t SuffixImageType = FileName2ImageType(Suffix);
		if( (ExplicitOutputImageType != imtUnknown) &&
			(SuffixImageType != ExplicitOutputImageType) )
		{
			tstring DefaultExt;
			GetDefaultImageExt(ExplicitOutputImageType, DefaultExt );
			AppendOrReplaceExt(DefaultExt, Suffix);
		}
	}
	else if ( ExportType == exp2Word )
	{
		if( !HasDocExt(Suffix) )
		{
			tstring DefaultExt;
			GetDefaultDocExt(DefaultExt);
			AppendOrReplaceExt(DefaultExt, Suffix);
		}
	}
	else
		_ASSERT( FALSE );
}

void CProgramOptions::AppendOrReplaceExt(const tstring& NewExt, tstring& OldExt /*In-Out*/ ) const
{
	tstring ExplicitOutputFileName;
	GetExplicitOutputFileName(ExplicitOutputFileName);
	if( !ExplicitOutputFileName.empty() &&
		(IsDirectoryName(ExplicitOutputFileName) || IsDirectory(ExplicitOutputFileName)) &&
		(IsImageExt(OldExt) || (IsDocExt(OldExt) && GetExportType() == exp2Word) ) // dirty hack
	  )
	{
		ChangeFileExt(OldExt, NewExt, OldExt, true /*Replace*/);
	}
	else
	{
		ChangeFileExt(OldExt, NewExt, OldExt, false /*Append*/);
	}
}

bool CProgramOptions::NeedAdditionalInfix() const
{
	tstring ExplicitOutputFileName;
	GetExplicitOutputFileName(ExplicitOutputFileName);
	if( !ExplicitOutputFileName.empty() &&
		!(IsDirectoryName(ExplicitOutputFileName) || IsDirectory(ExplicitOutputFileName))
	  )
		return true;
	return false; // Temp
}